package com.cash.channel.svc.logic;

import com.cash.channel.api.model.channel.ChannelModel;
import com.cash.channel.svc.dao.ChannelCallbackUrlMapper;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cash.channel.svc.annotation.ServiceLog;
import com.cash.channel.api.constant.Constant;
import com.cash.channel.api.model.channel.ChannelChangeLogModel;
import com.cash.channel.api.model.channel.ChannelModel;
import com.cash.channel.api.model.channel.RequireParam;
import com.cash.channel.api.util.ObjectUtil;
import com.cash.channel.svc.dao.ChannelChangeLogMapper;
import com.cash.channel.svc.dao.ChannelInfoMapper;
import com.cash.channel.svc.model.ChannelCallbackUrl;
import com.cash.channel.svc.model.ChannelChangeLog;
import com.cash.channel.svc.dao.ChannelRequireParamsMapper;
import com.cash.channel.svc.model.ChannelInfo;
import com.cash.channel.api.exception.CashChannelException;
import com.cash.channel.svc.model.ChannelRequireParams;
import com.cash.channel.svc.util.CreateKeys;
import com.cash.common.exception.BusinessException;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import geex.fundApply.model.ChannelFundQuato;
import geex.fundApply.req.quota.InsertChannelQuotaReq;
import geex.fundApply.req.quota.QueryChannelQuotaReq;
import geex.fundApply.req.quota.QueryChannelQuotaUsedReq;
import geex.fundApply.rsp.dto.ChannelQuotaDto;
import geex.fundApply.rsp.quota.InsertChannelQuotaRsp;
import geex.fundApply.rsp.quota.QueryChannelQuotaRsp;
import geex.fundApply.rsp.quota.QueryChannelQuotaUsedRsp;
import geex.fundApply.service.quota.ChannelQuotaService;
import geex.fundApply.service.quota.ChannelQuotaUsedService;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.regexp.RegexpUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Condition;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Component
public class ChannelLogic {

    @Autowired
    private ChannelInfoMapper channelInfoMapper;

    @Autowired
    private ChannelChangeLogMapper channelChangeLogMapper;

    @Autowired
    private ChannelRequireParamsMapper channelRequireParamsMapper;

    @Autowired
    private ChannelQuotaService channelQuotaService;

    @Resource
    private RedisTemplate redisTemplate;

    @Autowired
    private ChannelQuotaUsedService channelQuotaUsedService;

    private static final String[] credentialConfigs = new String[]{"isNew","notOverdue","notTransit"};

    private static final String[] requireParamsConfigs = new String[]{"OCR_VALID ","OCR_IDNO","OCR_NAME","OCR_BIRTHDAY","OCR_NATIONALITY",
            "OCR_GENDER","OCR_REGISTER_CITY","OCR_VALID_START","OCR_VALID_END","JOB_TYPE","SALARY","MARRIAGE",
            "SCHOLARSHIP","RELATION_1","RELATION_2","LIVING_CITY","LIVING_DETAIL","COMPANY_CITY","COMPANY_DETAIL",
            "COMPANY_NAME","CHILDREN_AMOUNT","LIVING_TYPE","COMPANY_PHONE","REGISTER_ADDRESS","EMAIL","JOB_TIME"};

    @Autowired
    private ChannelCallbackUrlMapper channelCallbackUrlMapper;

    public ChannelInfo getChannelInfoByCode(String channelCode) throws CashChannelException {
        Condition condition = new Condition(ChannelInfo.class);
        condition.createCriteria().andEqualTo("cChannel", channelCode);
        List<ChannelInfo> channelInfoList = channelInfoMapper.selectByCondition(condition);
        if (channelInfoList == null || channelInfoList.isEmpty()) {
            throw new CashChannelException("渠道[" + channelCode + "]不存在，请联系管理员");
        }
        if (channelInfoList.size() > 1) {
            throw new CashChannelException("渠道[" + channelCode + "]配置异常，请联系管理员");
        }
        ChannelInfo channelInfo = channelInfoList.get(0);
        return channelInfo;
    }

    @Transactional(rollbackFor = Exception.class)
    public ChannelModel saveChannelInfoByCode(ChannelModel channelModel) throws CashChannelException {
        boolean isNew = false;
        ChannelInfo record = new ChannelInfo();
        record.setcChannel(channelModel.getcChannel());
        List<ChannelInfo> channelInfos = channelInfoMapper.select(record);
//        ChannelInfo oldChannelInfo = new ChannelInfo();
        ChannelModel oldChannelModel = findChannelModelByCodeWithCreditPlatform(channelModel.getcChannel());
        ChannelInfo channelInfo = null;
        if (!channelInfos.isEmpty()) {
            channelInfo = channelInfos.get(0);
//            BeanUtils.copyProperties(channelInfo, oldChannelInfo);
        }
        if (channelInfo == null) {
            isNew = true;
            channelInfo = new ChannelInfo();
            channelInfo.setcPlatformAesKey(CreateKeys.getAESKey());
//            channelInfo.setcChannelAesKey(CreateKeys.getAESKey());
            channelInfo.setcChannelAesKey(channelInfo.getcPlatformAesKey());
            Map<String, String> channelRSAKeys = CreateKeys.getRSAKey();
            channelInfo.setcChannelPrivateKey(MapUtils.getString(channelRSAKeys, "priKey"));
            channelInfo.setcChannelPublicKey(MapUtils.getString(channelRSAKeys, "pubKey"));
            Map<String, String> platformRSAKeys = CreateKeys.getRSAKey();
            channelInfo.setcPlatformPrivateKey(MapUtils.getString(platformRSAKeys, "priKey"));
            channelInfo.setcPlatformPublicKey(MapUtils.getString(platformRSAKeys, "pubKey"));
            channelInfo.setcChannelId(createNewChannelId());
        } else {
            if (StringUtils.isEmpty(channelModel.getcChannelId())){
                throw new CashChannelException("渠道["+channelModel.getcChannel()+"]已存在");
            }
        }
        channelInfo.setcChannel(channelModel.getcChannel());
        channelInfo.setcProductCode(StringUtils.isEmpty(channelModel.getcProductCode()) ? channelModel.getcChannel() : channelModel.getcProductCode());
        channelInfo.setcChannelName(channelModel.getcChannelName());
        channelInfo.setnCustom(channelModel.getnCustom());
        channelInfo.setnOperatorStatus(channelModel.getnOperatorStatus());
        channelInfo.setcRequireParams(channelModel.getcRequireParams());
        channelInfo.setcCredentialConfig(channelModel.getcCredentialConfig());
        channelInfo.setnInUse(channelModel.getnInUse());
        if (isNew) {
            channelInfoMapper.insertSelective(channelInfo);
        } else {
            channelInfoMapper.updateByPrimaryKeySelective(channelInfo);
        }
        insertNewChannelQuata(channelModel);

        addChangeLog(oldChannelModel, channelModel, channelModel.getcChannel(), channelModel.getOperator(), channelModel.getOperatorName());
        channelModel = new ChannelModel(credentialConfigs, requireParamsConfigs);
        BeanUtils.copyProperties(channelInfo, channelModel);
        return channelModel;
    }

    public String getChannelCallbackUrl(String channel, String type) {
        Condition condition = new Condition(ChannelCallbackUrl.class);
        condition.createCriteria().andEqualTo("cChannel", channel).andEqualTo("cCallbackType", type);
        ChannelCallbackUrl channelCallbackUrl = channelCallbackUrlMapper.selectOneByExample(condition);
        if (channelCallbackUrl==null) {
            return null;
        }
        return channelCallbackUrl.getcUrl();
    }

    @SuppressWarnings("unchecked")
    private String createNewChannelId() {
        String prefix = "CJKD"+ DateFormatUtils.format(new Date(), "yyyyMMdd");
        String redisKey = "_CHANNEL_ID_SUFFIX_"+prefix;
        Object idObj = null;
        if ((idObj = this.redisTemplate.opsForValue().get(redisKey))==null) {
            idObj = 1;
            this.redisTemplate.opsForValue().set(redisKey, idObj);
        }
        int id = (int) idObj;
        this.redisTemplate.opsForValue().set(redisKey, id+1, 1, TimeUnit.DAYS);
        String idStr = StringUtils.leftPad(String.valueOf(id),3,"0");
        return prefix.concat(idStr);
    }

    private void insertNewChannelQuata(ChannelModel channelModel) throws CashChannelException {
        InsertChannelQuotaReq channelQuotaReq = new InsertChannelQuotaReq();
        channelQuotaReq.setChannelCode(channelModel.getcChannel());
        if (!RegexpUtils.getMatcher("^\\d*$").matches(channelModel.getDailyLimit())) {
            throw new CashChannelException("每日限额必须为数字");
        }
        channelQuotaReq.setChannelQuota(new BigDecimal(StringUtils.isEmpty(channelModel.getDailyLimit())?"0":channelModel.getDailyLimit()));
        ChannelFundQuato channelFundQuato = new ChannelFundQuato();
//        channelFundQuato.setFundId(channelModel.getCreditPlatformName());
//        channelFundQuato.setFundQuato(new BigDecimal(StringUtils.isEmpty(channelModel.getCreditPlatformDailyLimit())?"0":channelModel.getCreditPlatformDailyLimit()));
//        channelFundQuato.setStatus(channelModel.getCreditPlatformStatus());
        channelQuotaReq.setChannelFundQuatoList(Lists.newArrayList(channelFundQuato));
        log.info("资金端保存资金方信息："+JSON.toJSONString(channelQuotaReq));
        InsertChannelQuotaRsp insertChannelQuotaRsp = channelQuotaService.insertChannelQuota(channelQuotaReq);
        log.info("资金端返回："+JSON.toJSONString(insertChannelQuotaRsp));
        if (!insertChannelQuotaRsp.isSuccess()) {
            throw new BusinessException("保存资金方信息失败");
        }
    }

    @SuppressWarnings("unchecked")
    private void addChangeLog(ChannelModel oldChannelInfo, ChannelModel channelInfo, String channelCode, String operator,
                              String operatorName) {
        Map<String, Object> changeMap = ObjectUtil.getDifference(oldChannelInfo, channelInfo, "nId",
                "credentialConfigParams",
                "requireParamsConfigParams",
                "operatorName",
                "operator");
        if (MapUtils.getBoolean(changeMap, ObjectUtil.MAP_EQUAL)) {
            return;
        }
        Map<String, Object> originalDifferenceMap = MapUtils.getMap(changeMap, ObjectUtil.ORIGINAL_DIFFERENCE_MAP, Maps.newHashMap());
        Map<String, Object> newDifferenceMap = MapUtils.getMap(changeMap, ObjectUtil.NEW_DIFFERENCE_MAP, Maps.newHashMap());
        contrastObjectField(originalDifferenceMap, newDifferenceMap, "cCredentialConfig");
        contrastObjectField(originalDifferenceMap, newDifferenceMap, "cRequireParams");
        channelChangeLogMapper.insertSelective(new ChannelChangeLog(channelCode, operator, operatorName, new Date(),
                JSON.toJSONString(originalDifferenceMap), JSON.toJSONString(newDifferenceMap),
                oldChannelInfo == null ? Constant.CHANNEL_CHANGE_TYPE.新增.getType() : Constant.CHANNEL_CHANGE_TYPE.修改.getType()));
    }

    @SuppressWarnings("unchecked")
    private void contrastObjectField(Map<String, Object> originalDifferenceMap, Map<String, Object> newDifferenceMap, String fieldName) {
        if (StringUtils.isNotEmpty(MapUtils.getString(originalDifferenceMap, fieldName)) &&
                StringUtils.isNotEmpty(MapUtils.getString(newDifferenceMap, fieldName))) {
            Map<String, Object> oriCredentialConfig = JSON.parseObject(MapUtils.getString(originalDifferenceMap, fieldName));
            Map<String, Object> newCredentialConfig = JSON.parseObject(MapUtils.getString(newDifferenceMap, fieldName));
            originalDifferenceMap.remove(fieldName);
            newDifferenceMap.remove(fieldName);
            Map<String, Object> changeMap = ObjectUtil.getDifference(oriCredentialConfig, newCredentialConfig);
            if (MapUtils.getBoolean(changeMap, ObjectUtil.MAP_EQUAL)) {
                return;
            }
            originalDifferenceMap.putAll(MapUtils.getMap(changeMap, ObjectUtil.ORIGINAL_DIFFERENCE_MAP));
            newDifferenceMap.putAll(MapUtils.getMap(changeMap, ObjectUtil.NEW_DIFFERENCE_MAP));
        }
    }

    @ServiceLog("获取渠道修改记录列表")
    public List<ChannelChangeLogModel> findChangeLogList(String channelCode) {
        Condition condition = new Condition(ChannelChangeLog.class);
        condition.createCriteria().andEqualTo("cChannelCode", channelCode).andEqualTo("cOperationType", Constant.CHANNEL_CHANGE_TYPE.修改.getType());
        condition.orderBy("dChangeTime").desc();
        List<ChannelChangeLog> changeLogList = channelChangeLogMapper.selectByCondition(condition);
        List<ChannelChangeLogModel> changeLogModelList = Lists.newArrayList();
        ChannelChangeLogModel channelChangeLogModel = null;
        for (ChannelChangeLog channelChangeLog : changeLogList) {
            channelChangeLogModel = new ChannelChangeLogModel();
            BeanUtils.copyProperties(channelChangeLog, channelChangeLogModel);
            changeLogModelList.add(channelChangeLogModel);
        }
        return changeLogModelList;
    }

    @ServiceLog("获取渠道修改记录详情")
    public ChannelChangeLogModel getChangeLogModelById(Integer id) {
        ChannelChangeLog channelChangeLog = channelChangeLogMapper.selectByPrimaryKey(id);
        ChannelChangeLogModel channelChangeLogModel = new ChannelChangeLogModel();
        BeanUtils.copyProperties(channelChangeLog, channelChangeLogModel);
        return channelChangeLogModel;
    }

    @ServiceLog("获取渠道列表")
    public Map<String, Object> findChangeList(String channelName, Integer status, Integer page, Integer rows) throws CashChannelException {
        Map<String, Object> map = Maps.newHashMap();
        Condition condition = new Condition(ChannelInfo.class);
        Example.Criteria criteria = condition.createCriteria();
        if (!StringUtils.isEmpty(channelName)) {
            criteria.andLike("cChannelName", "%"+channelName+"%");
        }
        if (status!=null) {
            criteria.andEqualTo("nInUse", status);
        }
        page = page == null ? 1 : page;
        rows = rows == null ? 10 : rows;
        map.put("total", channelInfoMapper.selectCountByCondition(condition));
        log.info(JSON.toJSONString(channelInfoMapper.findChannelInfoList(channelName, status, (page - 1) * rows, rows)));
        map.put("list", insertChannelQuataChannelModels(channelInfoMapper.findChannelInfoList(channelName, status, (page - 1) * rows, rows).toArray(new ChannelModel[]{})));
        return map;
    }

    public ChannelModel findChannelModelByCodeWithCreditPlatform(String channelCode) throws CashChannelException {
        ChannelInfo record = new ChannelInfo();
        record.setcChannel(channelCode);
        ChannelInfo channelInfo = channelInfoMapper.selectOne(record);
        if (channelInfo==null) {
            return null;
        }
        ChannelModel channelModel = new ChannelModel(credentialConfigs, requireParamsConfigs);
        BeanUtils.copyProperties(channelInfo, channelModel);
        insertChannelQuataChannelModels(channelModel);
        return channelModel;
    }

    private ArrayList<ChannelModel> insertChannelQuataChannelModels(ChannelModel... channelModels) throws CashChannelException {
        Map<String, ChannelQuotaDto> channelQuotaDtoMap = queryChannelQuota(Arrays.stream(channelModels).map(ChannelModel::getcChannel).collect(Collectors.toList()).toArray(new String[]{}));
        for (ChannelModel channelModel : channelModels) {
            ChannelQuotaDto channelQuotaDto = channelQuotaDtoMap.get(channelModel.getcChannel());
            if (channelQuotaDto!=null && channelQuotaDto.getChannelQuota()!=null){
                channelModel.setDailyLimit(channelQuotaDto.getChannelQuota().toString());
                for (ChannelFundQuato channelFundQuato : channelQuotaDto.getChannelFundQuatoList()) {
//                    channelModel.setCreditPlatformName(channelFundQuato.getFundId());
//                    channelModel.setCreditPlatformStatus(channelFundQuato.getStatus());
//                    channelModel.setCreditPlatformDailyLimit(channelFundQuato.getFundQuato()==null?"":channelFundQuato.getFundQuato().toString());
                }
            }
        }
        return Lists.newArrayList(channelModels);
    }

    public Map<String, ChannelQuotaDto> queryChannelQuota(String... channelCode) throws CashChannelException {
        if (ArrayUtils.isEmpty(channelCode)) {
            return Maps.newHashMap();
        }
        QueryChannelQuotaReq req = new QueryChannelQuotaReq();
        req.setChannelCodeList(Lists.newArrayList(channelCode));
        log.info("资金端：获取资金方限额信息："+JSON.toJSONString(req));
        QueryChannelQuotaRsp rsp = channelQuotaService.queryChannelQuota(req);
        log.info("资金端：获取资金方限额信息返回："+JSON.toJSONString(rsp));
        if (!rsp.isSuccess()) {
            throw new CashChannelException("获取渠道限额失败");
        }
        Map<String, ChannelQuotaDto> resultMap = Maps.newHashMap();
        rsp.getChannelQuotaDtoList().stream().filter(channelQuotaDto -> StringUtils.isNotEmpty(channelQuotaDto.getChannelCode()))
                .forEach(channelQuotaDto -> resultMap.put(channelQuotaDto.getChannelCode(), channelQuotaDto));
        return resultMap;
    }

    public RequireParam checkParamsRequire(String channelCode) throws CashChannelException {
        ChannelInfo channelInfo = getChannelInfoByChannelId(channelCode);
        JSONObject requireParamJson = JSON.parseObject(channelInfo.getcRequireParams());
        List<String> paramSetList = requireParamJson.entrySet().stream().filter(param -> param.getValue().equals(true))
                .map(Map.Entry::getKey).collect(Collectors.toList());
        RequireParam requireParam = RequireParam.init();
        if (paramSetList.isEmpty()){
            return requireParam;
        }
        Condition condition = new Condition(ChannelRequireParams.class);
        condition.createCriteria().andIn("cRequireParamSet", paramSetList)
                .andEqualTo("cChannel", getRequireChannel(channelInfo));
        List<ChannelRequireParams> requireParamsBeanList = channelRequireParamsMapper.selectByCondition(condition);
        for (ChannelRequireParams channelRequireParams : requireParamsBeanList) {
            insertRequireParamsMap(requireParam, channelRequireParams);
        }
        return requireParam;
    }

    private String getRequireChannel(ChannelInfo channelInfo) throws CashChannelException {
        switch (channelInfo.getnCustom()) {
            case 1:
                return channelInfo.getcChannel();
            case 2:
                return Constant.CHANNEL.APP;
            case 0:
                return Constant.CHANNEL.COMMON;
            default:
                throw new CashChannelException("渠道配置错误");
        }
    }

    private void insertRequireParamsMap(RequireParam requireParam, ChannelRequireParams channelRequireParams) {
        String[] paramArr = channelRequireParams.getcThirdParamName().split("\\.");
        for (int i = 0; i < paramArr.length; i++) {
            requireParam = requireParam.appendSubRequireParam(i != paramArr.length - 1, paramArr[i]);
        }
        requireParam = requireParam.getRoot();
    }

    public void checkChannelQuota(String channel, BigDecimal loanAmount) throws CashChannelException {
        if (StringUtils.isEmpty(channel)) {
            throw new CashChannelException("渠道不能为空");
        }
        QueryChannelQuotaUsedReq req = new QueryChannelQuotaUsedReq();
        req.setChannelCode(channel);
        log.info("资金端：获取渠道限额:"+JSON.toJSONString(req));
        QueryChannelQuotaUsedRsp rsp = channelQuotaUsedService.queryChannelQuotaUsed(req);
        log.info("资金端：获取渠道限额返回:"+JSON.toJSONString(rsp));
        if (rsp.isSuccess()) {
            if (rsp.getChannelQuota().subtract(rsp.getChannelQuotaUsed()).compareTo(loanAmount)<0) {
                throw new CashChannelException("今日剩余额度不足，请明日再来试试吧");
            }
        } else {
            throw new CashChannelException("渠道配置错误[未配置额度]，请联系管理员");
        }
    }

    public ChannelInfo getChannelInfoByChannelId(String channel) throws CashChannelException {
        Condition condition = new Condition(ChannelInfo.class);
        condition.createCriteria().andEqualTo("cChannelId", channel);
        List<ChannelInfo> channelInfoList = channelInfoMapper.selectByCondition(condition);
        if (channelInfoList == null || channelInfoList.isEmpty()) {
            throw new CashChannelException("渠道[" + channel + "]不存在，请联系管理员");
        }
        if (channelInfoList.size() > 1) {
            throw new CashChannelException("渠道[" + channel + "]配置异常，请联系管理员");
        }
        ChannelInfo channelInfo = channelInfoList.get(0);
        return channelInfo;
    }
}
